package com.beidu.lottery.call.service.portal.impl;

import com.alibaba.fastjson.JSONObject;
import com.beidu.lottery.call.common.BaseContextHandler;
import com.beidu.lottery.call.constant.ActivityTypeEnum;
import com.beidu.lottery.call.constant.CacheKey;
import com.beidu.lottery.call.constant.LotteryLimitTypeEnum;
import com.beidu.lottery.call.constant.MQTopicsConst;
import com.beidu.lottery.call.dao.UserDataMapper;
import com.beidu.lottery.call.entity.PortalActivityRule;
import com.beidu.lottery.call.entity.dto.LotteryLeaveNumDTO;
import com.beidu.lottery.call.entity.mq.UserPointsDTO;
import com.beidu.lottery.call.entity.query.LotteryNumQuery;
import com.beidu.lottery.call.errorcode.CommonCode;
import com.beidu.lottery.call.exception.ServiceException;
import com.beidu.lottery.call.service.portal.ActivityRuleService;
import com.beidu.lottery.call.service.portal.UserDataService;
import com.beidu.lottery.call.utils.ActSequenceGen;
import com.beidu.lottery.call.utils.LocalDateUtils;
import com.beidu.lottery.call.utils.StockRedisUtil;
import com.beidu.lottery.call.utils.concurrent.ReentrantLockUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;

/**
 * <p>
 * 用户数据 服务实现类
 * </p>
 *
 * @author 北渡
 * @since 2023-08-23
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserDataServiceImpl implements UserDataService {
    //private final IntegralRecordClientApi integralRecordClientApi;
    private final StockRedisUtil stockRedisUtil;
    private final ReentrantLockUtil reentrantLockUtil;
    private final ActivityRuleService activityRuleService;
    private final UserDataMapper userDataMapper;
    private final KafkaTemplate<String, String> kafkaTemplate;
    private final RBloomFilter<String> lotteryNumQueryBloomFilter;
    private final RBloomFilter<String> activityExistBloomFilter;

    @Override
    public Long queryUserPoints() {
        // 调用积分查询API
//        @SuppressWarnings("rawtypes")
//        R r = integralRecordClientApi.getMyIntegral();
//        log.info("调用积分查询API, r => {}", r);
//        if (!r.getIsSuccess()) {
//            throw new ServiceException(CommonCode.QUERY_C_POINTS_FAIL.getMsg());
//        }
//        log.info("查询用户积分成功, userId => {}, points => {}", BaseContextHandler.getUserId(), r.getData());
//        return Long.parseLong(String.valueOf(r.getData()));
        return null; 
    }

    @Override
    public Integer queryRegRemainingQuota(PortalActivityRule rule) {
        log.info("queryRegRemainingQuota, rule => {}", rule);
        // 从Redis中读取剩余报名名额
        Integer quota = stockRedisUtil.get(CacheKey.genRegCounter(rule.getActivityId()));
        log.info("Redis中剩余报名名额, quota => {}", quota);
        if (quota != null) {
            return quota;
        }
        // 线程同步
        quota = reentrantLockUtil.executeWithLock("reg" + rule.getActivityId(), () -> {
            // 双检
            Integer r = stockRedisUtil.get(CacheKey.genRegCounter(rule.getActivityId()));
            if (r != null) {
                return r;
            }
            // 查询数据库中的剩余报名名额（SQL中计算方式：限额-已报名）
            r = userDataMapper.queryRegRemainingQuota(BaseContextHandler.getTenant(), rule);
            log.info("查询数据库中的剩余报名名额, r => {}", r);
            // 已结束
            if (LocalDateTime.now().isAfter(rule.getActivityEndTime())) {
                return r;
            }
            // Redis过期时间（活动结束时间）
            long timeout = LocalDateUtils.getTimestampTo(rule.getActivityEndTime());
            // 设置Redis中的剩余报名名额
            if (stockRedisUtil.setIfAbsent(CacheKey.genRegCounter(rule.getActivityId()), r, timeout)) {
                log.info("Redis中剩余报名名额设置完成, r => {}", r);
            }
            return r;
        });
        return quota;
    }

    @Override
    public String queryRegRemainingQuotaString(Integer id) {
        log.info("queryRegRemainingQuotaString, id => {}", id);
        // 查询活动规则
        PortalActivityRule rule = activityRuleService.getActivityRuleCache(id, ActivityTypeEnum.REG);
        if (rule == null) {
            throw new ServiceException(CommonCode.ACTIVITY_NOT_EXIST.getMsg());
        }

        // 已报名人数（计算方式：总数-剩余）
        int number = rule.getNumberLimit() - this.queryRegRemainingQuota(rule);

        // 字符串拼接
        StringBuilder sb = new StringBuilder();
        sb.append(number).append("/").append(rule.getNumberLimit());
        log.info("查询剩余报名名额字符串完成, sb => {}", sb);
        return sb.toString();
    }

    @Override
    public Integer queryVoteRemainingTimes(PortalActivityRule rule) {
        log.info("queryVoteRemainingTimes, rule => {}", rule);
        // 剩余投票次数
        Integer times;

        // 从Redis中读取剩余投票次数
        times = stockRedisUtil.get(CacheKey.genVoteCounter(rule.getActivityId()));
        log.info("Redis中剩余投票次数, times => {}", times);
        if (times != null) {
            return times;
        }

        // 查询数据库中的剩余投票次数（SQL中计算方式：限额-参与次数, 参与次数根据限额类型查询当日或者总数）
        times = userDataMapper.queryVoteRemainingTimes(BaseContextHandler.getTenant(), BaseContextHandler.getUserId(), rule);
        log.info("查询数据库中的剩余投票次数, times => {}", times);
        // 已结束
        if (LocalDateTime.now().isAfter(rule.getActivityEndTime())) {
            return times;
        }

        // Redis过期时间
        long timeout;
        if (rule.getVoteLimitType() == 0) {
            // 每日次数的过期时间
            timeout = LocalDateUtils.getTodayExpireTimestamp();
        } else {
            // 活动结束的过期时间
            timeout = LocalDateUtils.getTimestampTo(rule.getActivityEndTime());
        }
        // 设置Redis中的剩余投票次数
        if (stockRedisUtil.setIfAbsent(CacheKey.genVoteCounter(rule.getActivityId()), times, timeout)) {
            log.info("Redis中剩余投票次数设置完成, times => {}", times);
        }
        return times;
    }

    @Override
    public Integer queryVoteRemainingTimes(Integer id) {
        log.info("queryVoteRemainingTimes, id => {}", id);
        // 查询活动规则
        PortalActivityRule rule = activityRuleService.getActivityRuleCache(id, ActivityTypeEnum.VOTE);
        if (rule == null) {
            throw new ServiceException(CommonCode.ACTIVITY_NOT_EXIST.getMsg());
        }
        // 查询剩余投票次数
        Integer times = this.queryVoteRemainingTimes(rule);
        log.info("查询剩余投票次数完成, times => {}", times);
        return times;
    }

    @Override
    public void pointsOpPreSubmit(Integer points, Integer activityId) {
        // 组装DTO
        UserPointsDTO dto = UserPointsDTO.builder()
                .points(points)// 积分
                .activityId(activityId).build();// 活动ID
        dto.setBaseDTO(ActSequenceGen.genMQSeq());// MQ消息ID

        // 组装MQ消息
        JSONObject data = new JSONObject();
        data.put("dto", dto);
        log.info("组装积分发放MQ消息完成, data => {}", data);

        // 发送Kafka消息
        try {
            kafkaTemplate.send(MQTopicsConst.ACTIVITY_USER_POINTS_ADD, data.toJSONString());
        } catch (Exception e) {
            log.error("消息发送异常, 积分发放失败, userid => {}", BaseContextHandler.getUserId(), e);
            throw new ServiceException(CommonCode.USER_POINTS_ADD_ERROR.getMsg());
        }
    }

    @Override
    public void pointsOpSubmit(Integer points, Integer activityId) {
        // 组装DTO
//        IntegralRecordSaveDTO pointsDTO = IntegralRecordSaveDTO.builder()
//                .userId(BaseContextHandler.getUserId())// 用户ID
//                .dataSource(IntegralAcquisitionRuleEnum.EMS.code())// 数据来源：EMS
//                .integralValue(points)// 积分
//                .activityId(activityId.longValue())// 活动ID
//                .build();
//        log.info("调用积分更新API, pointsDTO => {}", pointsDTO);
//        @SuppressWarnings("rawtypes")
//        R r = integralRecordClientApi.addIntegralRecord(pointsDTO);
//        log.info("更新用户积分完成, r => {}", r);
//        if (!r.getIsSuccess()) {
//            throw new ServiceException(CommonCode.UPDATE_C_POINTS_FAIL.getMsg());
//        }
    }

    @Override
    public Integer queryLotteryRemainingTimes(PortalActivityRule rule) {
        log.info("queryLotteryRemainingTimes, rule => {}", rule);
        // 剩余抽奖次数
        Integer times;

        // 从Redis中读取剩余投票次数
        times = stockRedisUtil.get(CacheKey.genLotteryCounter(rule.getActivityId()));
        log.info("Redis中剩余抽奖次数, times => {}", times);
        if (times != null) {
            return times;
        }

        // 查询数据库中的剩余抽奖次数（SQL中计算方式：限额-参与次数+助力次数(邀请次数*规则因数), 参与次数根据限额类型查询当日或者总数）
        times = userDataMapper.queryLotteryRemainingTimes(BaseContextHandler.getTenant(), BaseContextHandler.getUserId(), rule);
        log.info("查询数据库中的剩余抽奖次数, times => {}", times);
        // 已结束
        if (LocalDateTime.now().isAfter(rule.getActivityEndTime())) {
            return times;
        }

        // Redis过期时间
        long timeout;
        if (rule.getLotteryLimitType() == 1) {
            // 每日次数的过期时间
            timeout = LocalDateUtils.getTodayExpireTimestamp();
        } else {
            // 活动结束的过期时间
            timeout = LocalDateUtils.getTimestampTo(rule.getActivityEndTime());
        }
        // 设置Redis中的剩余抽奖次数
        if (stockRedisUtil.setIfAbsent(CacheKey.genLotteryCounter(rule.getActivityId()), times, timeout)) {
            log.info("Redis中剩余抽奖次数设置完成, times => {}", times);
        }
        return times;
    }

    @Override
    public Integer queryLotteryRemainingTimes(LotteryLeaveNumDTO request) {
        Integer activityId = request.getActivityId();
        log.info("queryLotteryRemainingTimes, activityId => {}", activityId);
        // 查询活动规则
        PortalActivityRule rule = activityRuleService.getActivityRuleCache(activityId, ActivityTypeEnum.LOTTERY);
        if (rule == null) {
            throw new ServiceException(CommonCode.ACTIVITY_NOT_EXIST.getMsg());
        }
        // 不限制次数
        if (rule.getLotteryLimitType() != null && rule.getLotteryLimitType() == 0) {
            return -1;
        }
        // 查询剩余抽奖次数
        Integer times = this.queryLotteryRemainingTimes(rule);
        log.info("查询剩余抽奖次数完成, times => {}", times);
        return times;
    }

    @Override
    public Integer queryLotteryRemainingQuota(PortalActivityRule rule) {
        log.info("queryLotteryRemainingQuota, rule => {}", rule);
        // 从Redis中读取剩余中奖名额
        Integer quota = stockRedisUtil.get(CacheKey.genWinQuotaKey(rule.getActivityId()));
        log.info("Redis中剩余中奖名额, quota => {}", quota);
        if (quota != null) {
            return quota;
        }
        // 线程同步
        quota = reentrantLockUtil.executeWithLock("lottery" + rule.getActivityId(), () -> {
            // 双检
            Integer r = stockRedisUtil.get(CacheKey.genWinQuotaKey(rule.getActivityId()));
            if (r != null) {
                return r;
            }
            // 查询数据库中的剩余中奖名额（计算方式：中奖次数阈值-订单数）
            r = userDataMapper.queryLotteryRemainingQuota(BaseContextHandler.getTenant(), rule);
            log.info("查询数据库中的剩余中奖名额, r => {}", r);
            // 设置Redis中的剩余中奖名额
            if (stockRedisUtil.setIfAbsent(CacheKey.genWinQuotaKey(rule.getActivityId()), r)) {
                log.info("Redis中剩余中奖名额设置完成, r => {}", r);
            }
            return r;
        });
        return quota;
    }

    @Override
    public Integer queryLotteryLeaveNum(LotteryLeaveNumDTO req) {
        String userId = req.getUserId();
        log.info("{}查询剩余抽奖次数", userId);
        // 查询活动规则
        PortalActivityRule rule = activityRuleService.getActivityRuleCache(req.getActivityId(), ActivityTypeEnum.LOTTERY);
        if (rule == null) {
            throw new ServiceException(CommonCode.ACTIVITY_NOT_EXIST.getMsg());
        }
        // 剩余抽奖次数
        Integer leaveNum;
        // 不限制次数
        if (rule.getLotteryLimitType() != null && rule.getLotteryLimitType() == 0) {
            leaveNum = -1;
        } else {
            String counterKey = CacheKey.genLotteryCounter(rule.getActivityId());
            // 从Redis中读取剩余投票次数
            leaveNum = stockRedisUtil.get(counterKey);
            log.info("{}缓存中剩余抽奖次数为=> {}", userId, leaveNum);
            if (leaveNum != null) {
                return leaveNum;
            }
            // 查询参与次数
            Integer invokeNum = queryLotteryInvokeNum(req);
            // 规则抽奖限额-已抽奖次数=剩余次数
            if (Objects.equals(3, rule.getLotteryLimitType())) {
                leaveNum = rule.getAccountLotteryLimit() - invokeNum;
            }
            log.info("{}已抽奖次数为{}剩余次数为{}", userId, invokeNum, leaveNum);
            // 设置Redis中的剩余抽奖次数
            if (stockRedisUtil.setIfAbsent(counterKey, leaveNum, LocalDateUtils.calWeekSeconds(null))) {
                log.info("{}剩余抽奖次数添加缓存完成", userId);
            }
        }
        return leaveNum;
    }

    @Override
    public Integer queryLotteryLeaveNumBloomFilter(LotteryLeaveNumDTO req) {
        String userId = req.getUserId();
        log.info("{}查询剩余抽奖次数", userId);
        // 查询活动规则
        PortalActivityRule rule = activityRuleService.getActivityRuleCache(req.getActivityId(), ActivityTypeEnum.LOTTERY);
        // 剩余抽奖次数
        Integer leaveNum = 0;
        // 不限制次数
        if (Objects.equals(LotteryLimitTypeEnum.NO_LIMIT.getCode(), rule.getLotteryLimitType())) {
            leaveNum = -1;
        } else {
            // 判断布隆过滤器是否存在用户ID，不存在说明无抽奖资格，返回0次
            if (!lotteryNumQueryBloomFilter.contains(userId)) {
                log.info("布隆过滤器中不存在此用户id");
                return 0;
            }
            String counterKey = CacheKey.genLotteryCounter(rule.getActivityId());
            // 从Redis中读取剩余抽奖次数
            leaveNum = stockRedisUtil.get(counterKey);
            log.info("{}缓存中剩余抽奖次数为=> {}", userId, leaveNum);
            if (leaveNum != null) {
                return leaveNum;
            }
            // 查询数据库已参与次数
            Integer invokeNum = queryLotteryInvokeNum(req);
            // 规则抽奖限额-已抽奖次数=剩余次数
            leaveNum = rule.getAccountLotteryLimit() - invokeNum;
            log.info("{}已抽奖次数为{}剩余次数为{}", userId, invokeNum, leaveNum);
            // 设置Redis中的剩余抽奖次数
            if (leaveNum > 0) {
                // Redis过期时间
                long timeout;
                if (Objects.equals(LotteryLimitTypeEnum.EVERYDAY.getCode(), rule.getLotteryLimitType())) {
                    // 每日次数的过期时间
                    timeout = LocalDateUtils.getTodayExpireTimestamp();
                } else {
                    // 活动结束的过期时间
                    timeout = LocalDateUtils.getTimestampTo(rule.getActivityEndTime());
                }
                stockRedisUtil.setIfAbsent(counterKey, leaveNum, timeout);
                log.info("{}剩余抽奖次数添加缓存完成", userId);
            }
        }
        return leaveNum;
    }

    @Override
    public void addLotteryTimes(LotteryLeaveNumDTO req) {
        String userId = req.getUserId();
        // 添加用户到布隆过滤器中
        lotteryNumQueryBloomFilter.add(userId);
        log.info("{}添加到抽奖次数布隆过滤器中完成", userId);
    }

    @Override
    public void addActivityId(LotteryLeaveNumDTO req) {
        String activityId = String.valueOf(req.getActivityId());
        // 添加活动id到布隆过滤器中
        activityExistBloomFilter.add(activityId);
        log.info("{}添加到活动id布隆过滤器中完成", activityId);
    }

    /**
     * 查询已抽奖次数
     *
     * @param req 请求参数
     */
    public Integer queryLotteryInvokeNum(LotteryLeaveNumDTO req) {
        String userId = req.getUserId();
        // 查询剩余抽奖次数
        LocalDate startDate = StringUtils.isEmpty(req.getStartDate()) ? LocalDate.now() : LocalDate.parse(req.getStartDate());
        LocalDate endDate = StringUtils.isEmpty(req.getEndDate()) ? LocalDateUtils.lastDayOfWeek(startDate) : LocalDate.parse(req.getEndDate());
        return userDataMapper.queryLotteryNum(LotteryNumQuery.builder()
                .userId(userId)
                .activityId(req.getActivityId())
                .startDate(startDate)
                .endDate(endDate)
                .build());
    }


}
